package org.example.judge.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.example.api.domain.UserExeResult;
import org.example.api.domain.dto.JudgeSubmitDTO;
import org.example.api.domain.vo.UserQuestionResultVO;
import org.example.common.core.constants.Constants;
import org.example.common.core.constants.JudgeConstants;
import org.example.common.core.enums.CodeRunStatus;
import org.example.judge.domain.SandBoxExecuteResult;
import org.example.judge.domain.UserSubmit;
import org.example.judge.mapper.UserSubmitMapper;
import org.example.judge.service.JudgeService;
import org.example.judge.service.SandboxPoolService;
import org.example.judge.service.SandboxService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service
public class JudgeServiceImpl implements JudgeService {

    //代码的运行操作和逻辑都是在这个沙盒中进行
    @Autowired
    private SandboxService sandboxService;

    @Autowired
    private UserSubmitMapper userSubmitMapper;

    //docker容器线程池
    @Autowired
    private SandboxPoolService sandboxPoolService;

    /**
     *此处只是对用户提交代码后时，进行对其结果的比对，比如时间和空间是否超出，代码运行后的代码是否是预期结果等等
     * @param judgeSubmitDTO
     * @return
     */
    public UserQuestionResultVO doJudgeJavaCode(JudgeSubmitDTO judgeSubmitDTO) {
        //从沙盒中获取运行结果
//        SandBoxExecuteResult sandBoxExecuteResult =
//                sandboxService.exeJavaCode(judgeSubmitDTO.getUserId(),
//                judgeSubmitDTO.getUserCode(),
//                judgeSubmitDTO.getInputList());

        //使用docker池，来运行用户提交的代码
        SandBoxExecuteResult sandBoxExecuteResult =
                sandboxPoolService.exeJavaCode(judgeSubmitDTO.getUserId(), judgeSubmitDTO.getUserCode(), judgeSubmitDTO.getInputList());

        UserQuestionResultVO userQuestionResultVO = new UserQuestionResultVO();
        //判断运行结果
        if (sandBoxExecuteResult != null && CodeRunStatus.SUCCEED.equals(sandBoxExecuteResult.getRunStatus())) {
            //说明此时代码运行成功，并没有其他问题 接下来进行代码实际结果和预期结果的比对  是否超出时间限制和空间限制的比对
            userQuestionResultVO = doJudge(judgeSubmitDTO, sandBoxExecuteResult, userQuestionResultVO);
        }else {
            //此时代码运行失败(代码有异常)，需要把信息返回  并需要把属性赋值给UserQuestionResultVO
            userQuestionResultVO.setPass(Constants.FALSE);
            if (sandBoxExecuteResult != null) {
                userQuestionResultVO.setExeMessage(sandBoxExecuteResult.getExeMessage());
            }else {
                userQuestionResultVO.setExeMessage(CodeRunStatus.UNKNOWN_FAILED.getMsg());
            }
            userQuestionResultVO.setScore(JudgeConstants.ERROR_SCORE);
        }
        saveUserSubmit(judgeSubmitDTO, userQuestionResultVO);
        return userQuestionResultVO;
    }

    private static UserQuestionResultVO doJudge(JudgeSubmitDTO judgeSubmitDTO,
                                                SandBoxExecuteResult sandBoxExecuteResult,
                                                UserQuestionResultVO userQuestionResultVO) {
        //先判断代码运行的结果是否符合预期结果
        List<String> exeOutputList = sandBoxExecuteResult.getOutputList();
        List<String> outputList = judgeSubmitDTO.getOutputList();
        if (exeOutputList.size() != outputList.size()) {
            //说明实际运行的代码结果和预期运行的代码结果不同,   对分数 是否通过 异常情况进行设置
            userQuestionResultVO.setScore(JudgeConstants.ERROR_SCORE);
            userQuestionResultVO.setPass(Constants.FALSE);
            userQuestionResultVO.setExeMessage(CodeRunStatus.NOT_ALL_PASSED.getMsg());
            return userQuestionResultVO;
        }
        //数量符合预期的代码数量，此时就需要判断代码每个实际输出的结果和预期的结果是否相同
        //并且还需要劲这个UserExeResult的列表记录下来，如果实际结果出现问题还能够给用户进行提示效果
        List<UserExeResult> userExeResultList = new ArrayList<>();
        boolean pass = resultCompare(judgeSubmitDTO, exeOutputList, outputList, userExeResultList);
        return assembleUserQuestionResultVO(judgeSubmitDTO, sandBoxExecuteResult, userQuestionResultVO, userExeResultList, pass);
    }

    private static UserQuestionResultVO assembleUserQuestionResultVO(JudgeSubmitDTO judgeSubmitDTO,
                                                                     SandBoxExecuteResult sandBoxExecuteResult,
                                                                     UserQuestionResultVO userQuestionResultVO,
                                                                     List<UserExeResult> userExeResultList, boolean pass) {
        //将这个List<UserExeResult>结果传递给返回前端的对象当中
        userQuestionResultVO.setUserExeResultList(userExeResultList);
        //判断此时pass是否为false，为false则说明实际输出结果和预期输出的结果不同
        if (!pass) {
            userQuestionResultVO.setScore(JudgeConstants.ERROR_SCORE);
            userQuestionResultVO.setPass(Constants.FALSE);
            userQuestionResultVO.setExeMessage(CodeRunStatus.NOT_ALL_PASSED.getMsg());
            return userQuestionResultVO;
        }
        //此时通过了结果的比对，还需要判断此时是否超出空间限制和时间限制
        if (sandBoxExecuteResult.getUseMemory() > judgeSubmitDTO.getSpaceLimit()) {
            userQuestionResultVO.setScore(JudgeConstants.ERROR_SCORE);
            userQuestionResultVO.setPass(Constants.FALSE);
            userQuestionResultVO.setExeMessage(CodeRunStatus.OUT_OF_MEMORY.getMsg());
            return userQuestionResultVO;
        }
        if (sandBoxExecuteResult.getUseTime() > judgeSubmitDTO.getTimeLimit()) {
            userQuestionResultVO.setScore(JudgeConstants.ERROR_SCORE);
            userQuestionResultVO.setPass(Constants.FALSE);
            userQuestionResultVO.setExeMessage(CodeRunStatus.OUT_OF_TIME.getMsg());
            return userQuestionResultVO;
        }

        //到这里就说明，代码运行全部通过了
        userQuestionResultVO.setPass(Constants.TRUE);
        //分数是通过困难度和基础分数来计算出来的
        int score = judgeSubmitDTO.getDifficulty() * JudgeConstants.DEFAULT_SCORE;
        userQuestionResultVO.setScore(score);
        return userQuestionResultVO;
    }

    private static boolean resultCompare(JudgeSubmitDTO judgeSubmitDTO,
                                         List<String> exeOutputList,
                                         List<String> outputList,
                                         List<UserExeResult> userExeResultList) {
        boolean pass = true;
        for (int index = 0; index < exeOutputList.size(); index++) {
            //实际输出结果
            String exeOutput = exeOutputList.get(index);
            //预期输出结果
            String output = outputList.get(index);
            //输入
            String input = judgeSubmitDTO.getInputList().get(index);
            //将上述的信息加入到UserExeResult
            UserExeResult userExeResult = new UserExeResult();
            userExeResult.setExeOutput(exeOutput);
            userExeResult.setInput(input);
            userExeResult.setOutput(output);
            userExeResultList.add(userExeResult);
            //此处进行判断是否相同(预期结果和实际结果)
            if (!output.equals(exeOutput)) {
                pass = false;
            }
        }
        return pass;
    }

    private void saveUserSubmit(JudgeSubmitDTO judgeSubmitDTO, UserQuestionResultVO userQuestionResultVO) {
        //此时数据库中还需要插入一条用户最后一次提交的信息
        UserSubmit userSubmit = new UserSubmit();
        BeanUtil.copyProperties(userQuestionResultVO, userSubmit);
        userSubmit.setUserId(judgeSubmitDTO.getUserId());
        userSubmit.setQuestionId(judgeSubmitDTO.getQuestionId());
        userSubmit.setExamId(judgeSubmitDTO.getExamId());
        userSubmit.setProgramType(judgeSubmitDTO.getProgramType());
        userSubmit.setUserCode(judgeSubmitDTO.getUserCode());
        userSubmit.setCreateBy(judgeSubmitDTO.getUserId());
        userSubmit.setCaseJudgeRes(JSON.toJSONString(userQuestionResultVO.getUserExeResultList()));
        //因为需要的是最后一次的提交记录，所以先删除之前的提交记录，然后再进行新增一条最后一次的提交记录
        //由于submitId无法获取上一次的值，所以通过examId(前提是要存在,因为有可能不是竞赛，只是再写题目),questionId和userId来选择对应的提交数据信息
        userSubmitMapper.delete(new LambdaQueryWrapper<UserSubmit>()
                .eq(UserSubmit::getUserId, judgeSubmitDTO.getUserId())
                .eq(UserSubmit::getQuestionId, judgeSubmitDTO.getQuestionId())
                //当judgeSubmitDTO.getExamId() == null,执行这条语句 且这条语句时指的是 examId is NULL(SQL中的写法)
                .isNull(judgeSubmitDTO.getExamId() == null,UserSubmit::getExamId)
                //当judgeSubmitDTO.getExamId() != null 执行这条eq语句
                .eq(judgeSubmitDTO.getExamId() != null, UserSubmit::getExamId, judgeSubmitDTO.getExamId()));
        userSubmitMapper.insert(userSubmit);
    }
}
